Previous Book Contents Book Index Next

Inside Macintosh: 3D Graphics Programming With QuickDraw 3D /
Chapter 14 - Shader Objects


Using Shader Objects

QuickDraw 3D supplies routines that you use to create and configure shader objects. You can make a shader's effects appear in a rendered image in several ways. You can submit the shader inside a rendering loop, or you can add the shader to a group and submit the group inside a rendering loop. Indeed, you can apply a surface shader in yet a third way, by attaching it to an object as an attribute. These ways of applying a shader are all equally good, and which of them you use depends on the circumstances. For instance, if you put a shader object into an unordered display group, it will affect only the objects following it in the group.

Using Illumination Shaders

You create an illumination shader by calling the _New function for the type of illumination model you want to use. For example, to use Phong illumination, you can call the Q3PhongIllumination_New function.

Once you've created an illumination shader, you apply it to the objects in a model by submitting the shader inside of a submitting loop, or by adding it to a group that is submitted in a submitting loop. For instance, to apply Phong illumination to all the objects in a model, you can call the function Q3Shader_Submit in your rendering loop, as shown in Listing 14-1.

Listing 14-1 Applying an illumination shader

Q3View_StartRendering(myView);
do {
   Q3Shader_Submit(myPhongShader, myView);

   /*submit styles, groups, and other objects here*/

   myViewStatus = Q3View_EndRendering(myView);
} while (myViewStatus == kQ3ViewStatusRetraverse);

Using Texture Shaders

You create a texture shader by calling the Q3TextureShader_New function, to which you pass a texture object. QuickDraw 3D currently supports only pixmap texture objects, which you create by calling the Q3PixMapTexture_New function.

Once you've created a texture shader, you can apply it to all the objects in a model by submitting the shader inside of a rendering loop, as shown in Listing 14-2.

Listing 14-2 Applying a texture shader in a submitting loop

Q3View_StartRendering(myView);
do {
   Q3Shader_Submit(myTextureShader, myView);

   /*submit styles, groups, and other objects here*/

   myViewStatus = Q3View_EndRendering(myView);
} while (myViewStatus == kQ3ViewStatusRetraverse);
You can apply the shader to the objects in a group by adding it to a group that is submitted in a rendering loop, as shown in Listing 14-3. (The myGroup group is an ordered display group.)

Listing 14-3 Applying a texture shader in a group

Q3Group_AddObject(myGroup, myTextureShader);

Q3View_StartRendering(myView);
do {
   Q3Group_Submit(myGroup, myView);
   myViewStatus = Q3View_EndRendering(myView);
} while (myViewStatus == kQ3ViewStatusRetraverse);
You can also apply a texture shader to all the objects in a model by adding the shader as an attribute of type kQ3AttributeTypeSurfaceShader to the view's attribute set. Similarly, you can attach the texture shader to a part of a geometric object as an attribute. For example, you can attach a texture shader to the face of a cube or a mesh to have that face shaded with a texture. Listing 14-4 illustrates how to create a texture shader and use it to shade a triangle. Note that the function MyCreateShadedTriangle defined in Listing 14-4 sets up a custom surface parameterization for the triangle, because there is no standard surface parameterization for a triangle.

Listing 14-4 Applying a texture shader as an attribute

TQ3GeometryObject MyCreateShadedTriangle (TQ3StoragePixmap myPixmap)
{
   TQ3ShaderObject            myShader;
   TQ3TextureObject           myTexture;
   TQ3TriangleData            myTriData;
   TQ3GeometryObject          myTriangle;
   TQ3Param2D                 myParam2D;
   TQ3Vertex3D                myVertices[3] = {
         { { 0.5,  0.5, 0.0}, NULL },
         { {-0.5,  0.5, 0.0}, NULL },
         { {-0.5, -0.5, 0.0}, NULL }};
   /*Create a new texture from the pixmap passed in.*/
   myTexture = Q3PixmapTexture_New(&myPixmap);
   if (myTexture == NULL)
      return (NULL);
   Q3Object_Dispose(myPixmap.image);

   /*Create a new texture shader from the texture.*/
   myShader = Q3TextureShader_New(myTexture);
   if (myShader == NULL)
      return (NULL);
   Q3Object_Dispose(myTexture);

   /*Configure triangle data.*/
   /*First, attach uv values to the three vertices.*/
   myParam2D.u = 0;
   myParam2D.v = 0;
   myVertices[0].attributeSet = Q3AttributeSet_New();
   Q3AttributeSet_Add(myVertices[0].attributeSet, kQ3AttributeTypeShadingUV, 
                                    &myParam2D);
   myParam2D.u = 0;
   myParam2D.v = 1;
   myVertices[1].attributeSet = Q3AttributeSet_New();
   Q3AttributeSet_Add(myVertices[1].attributeSet, kQ3AttributeTypeShadingUV,
                                    &myParam2D);
   myParam2D.u = 1;
   myParam2D.v = 1;
   myVertices[2].attributeSet = Q3AttributeSet_New();
   Q3AttributeSet_Add(myVertices[2].attributeSet, kQ3AttributeTypeShadingUV, 
                                    &myParam2D);

   /*Define the triangle, using the vertices and uv values just set up.*/
   myTriData.vertices[0] = myVertices[0];
   myTriData.vertices[1] = myVertices[1];
   myTriData.vertices[2] = myVertices[2];
   
   /*Attach a texture surface shader as an attribute.*/
   myTriData.triangleAttributeSet = Q3AttributeSet_New();
   Q3AttributeSet_Add(myTriData.triangleAttributeSet, 
                              kQ3AttributeTypeSurfaceShader, &myShader);

   myTriangle = Q3Triangle_New(&myTriData);

   Q3Object_Dispose(myVertices[0].attributeSet);
   Q3Object_Dispose(myVertices[1].attributeSet);
   Q3Object_Dispose(myVertices[2].attributeSet);

   return(myTriangle);
}
The function MyCreateShadedTriangle defined in Listing 14-4 creates a texture from the pixmap it is passed and then creates a new texture shader from that texture. MyCreateShadedTriangle then attaches uv parameterization values to each of the three triangle vertices and defines the triangle data. Finally, MyCreateShadedTriangle creates a triangle and returns it to its caller. When the triangle is drawn (perhaps by being submitted in a rendering loop), it will have the specified texture mapped onto it.

Creating Storage Pixmaps

The data passed to the Q3PixmapTexture_New function (as in Listing 14-4 on page 14-12) is a storage pixmap, of type TQ3StoragePixmap. The image field of a storage pixmap specifies a storage object that contains the pixmap data to be applied as a texture. You can call either Q3MemoryStorage_New or Q3MemoryStorage_NewBuffer to create a storage object. Which function you use depends on whether (1) you want QuickDraw 3D to maintain the image data in an internal buffer or (2) you want to maintain the data in your own buffer.

To let QuickDraw 3D manage the pixmap data, you can assign the image field of a storage pixmap using code like this:

myStoragePixmap.image = Q3MemoryStorage_New(myBuffer, mySize);
This code asks QuickDraw 3D to allocate a buffer internally, of the specified size. Once Q3MemoryStorage_New returns successfully, you can dispose of the buffer myBuffer, because QuickDraw 3D has copied the texture pixmap data into its own internal memory.

If you prefer, you can maintain the pixmap data in your application's memory partition and avoid the overhead of having the data copied to internal QuickDraw 3D memory. (This is especially useful if you want to animate a texture by changing the texture pixmap data from frame to frame.) To do this, you create a storage object by calling the Q3MemoryStorage_NewBuffer function, like this:

myStoragePixmap.image = Q3MemoryStorage_NewBuffer
                                 (myBuffer, mySize, mySize);
In this case, you should not dispose of the data buffer. You can change the pixmap data by calling Q3MemoryStorage_SetBuffer.

Q3MemoryStorage_SetBuffer
            (myStoragePixmap.image, myBuffer, mySize, mySize);
You need to call Q3MemoryStorage_SetBuffer to force QuickDraw 3D to update any caches.

Note
You can also change the data of a storage object created by a call to Q3MemoryStorage_New, by calling Q3MemoryStorage_Set.

Handling uv Values Outside the Valid Range

As you've seen, a uv parameterization defines how to map one object (for example, a pixmap) onto another (typically a surface). The standard surface parameterizations defined by QuickDraw 3D all use u and v parametric values that are in the valid range 0.0 to 1.0. A custom surface parameterization, however, is free to define some other range of u and v values. When this happens, you need to indicate how you want QuickDraw 3D to handle uv values outside the valid range.

Currently, QuickDraw 3D supports two boundary-handling methods: wrapping and clamping. To wrap a shader effect is to replicate the entire effect across the mapped area. For example, to wrap a texture is to replicate the texture across the entire mapped area, as many times as are necessary to fill the mapped area. To clamp a shader effect is to replicate the boundaries of the effect across the portion of the mapped area that lies outside the valid range 0.0 to 1.0.

You can specify the boundary-handling methods of the u and v directions independently. You can call the Q3Shader_SetUBoundary function to indicate how to handle values in the u parametric direction that lie outside the valid range, and you can call the Q3Shader_SetVBoundary function to indicate how to handle values in the v parametric direction that lie outside the valid range. The default boundary-handling method is to wrap in both the u and v parametric directions.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
11 JUL 1996